day 15 - Self-Replicating Toy
[general]
day 15 - Self Replicating Toy
Can you design your own self-replicating toy? Service:
nc 3.93.128.89 1214
Download:chal.c
Recon
We're given a program in C that implements some virtual machine with a custom instruction set. The program accepts a program written for the VM from stdin and checks that the program's output is the program itself. If so, it prints the flag from flag.txt
.
The idea is straightforward, we need to implement a program for that VM that is a quine.
Code
#!/usr/bin/env python
from functools import partial
from pwn import *
FTABLE_IDX = 0
def push(s):
o = ord(s)
if o < 0x80:
return s
return '{}\x80'.format(chr(o ^ 0x80))
def pushlist(l):
pushseq = list(map(push, l))[::-1]
return ''.join(pushseq)
def fun(inst, idx):
return push('\xa1') + pushlist(inst) + push(chr(idx)) + '\xa0'
def call(idx):
absidx = idx + 0xc0
if absidx >= 0xe0:
raise ValueError('Invalid function call index')
return chr(absidx)
def ccall(idx):
absidx = idx + 0xe0
if absidx >= 0xe0 + 0x20:
raise ValueError('Invalid function call index')
return chr(absidx)
def binops(x):
return x
def binop(x, a1, a2):
return push(chr(a1)) + push(chr(a2)) + x
def dups():
return '\x91'
def dup(a):
return push(chr(a)) + dups()
def out(*args):
return '\xb0'
def printf(s):
return pushlist(s) + ''.join([out() for _ in s])
band = partial(binop, '\x82')
bands = partial(binops, '\x82')
bor = partial(binop, '\x83')
bors = partial(binops, '\x82')
bxor = partial(binop, '\x84')
bxors = partial(binops, '\x84')
swap = partial(binop, '\x90')
swaps = partial(binops, '\x90')
tests = partial(binops, '\x81')
def test():
out_shit = printf('Hello, world!')
mfun = fun(out_shit, 0)
return mfun + call(0)
def program():
# data function representation
# \x00 (signifies end of data) .. c0 \x01 c1 \x01 c2 \x01 ...
def reps(s):
pushes = [push(x) for x in s]
string = '\x00'
for x in pushes:
string += x
string += push('\x01')
return string
## OUTPUT 1 char
output = fun(out(), 1)
# called if and only if (iff) there is a char on the stack that is larger than 0x80
# outputs the quoted representation of the instruction that generates the character
funx = fun(swaps() + push('\x00') + out() + push('\x80') + out() + push('\x80') + bxors() + out(), 2)
# for current char c on stack
# check if c >= 0x80, if so call funx to print the quoted representation of a char >= 0x80
# if not, print the character directly
redo_p = fun(dups() + push('\x80') + bands() + dups() + ccall(2) + push('\x80') + bxors() + ccall(1), 3)
# Call redo_p in a loop, then go back to loop start p_s_q_loop
out_s_q_loop = fun(call(3) + call(5), 4)
# Outputs the current control character in the data rep (either \x00 or \x01)
# then calls out_s_q_loop to finalize the quote loop
p_s_q_loop = fun(dups() + out() + ccall(4), 5)
# Starts the quote loop, recreates the function structure then calls p_s_q_loop to start the
# print loop of the data, then finalizes the function structure
p_s_quoted = fun(push('\x21') + out() + push('\x80') + out() + call(5) + push('\x00') + out() + push('\xa0') + out(), 6)
# Print current data on the stack in a loop
out_s = fun(out() + call(8), 7)
p_s = fun(ccall(7), 8)
# Full program spec
# define the functions above then
# put the program on the stack
# print it quoted
# put it on the stack again
# print the program unquoted
prog = output + funx + redo_p + out_s_q_loop + p_s_q_loop + p_s_quoted + out_s + p_s + call(0) + call(6) + call(0) + call(8)
# data function describing the program itself
cfun = fun(reps(prog[::-1]), 0)
print("======================= DATA =================")
print(hexdump(cfun))
print("======================= DATA =================")
print("======================= PROGRAM =================")
print(hexdump(prog))
print("======================= PROGRAM =================")
return cfun + prog
if __name__ == '__main__':
prog_bin = program()
print("==========PROGRAM===========")
print(hexdump(prog_bin))
print("==========PROGRAM===========")
#p = process("./a.out")
p = remote("3.93.128.89", 1214)
p.readuntil("Length of your Assemblium sequence: ")
p.send("%d\n" % len(prog_bin))
p.readuntil("Enter your Assemblium sequence:\n")
p.send(prog_bin)
p.interactive()
Flag
AOTW{G0od_job_writing_y0ur_v3ry_0wN_quin3!}
More information
This is a py3 quine that uses the exact same strategy:
s = [
'',
'def pq():',
" print('s = [')",
' for i, v in enumerate(s):',
" print(' ' * 2 + repr(v), end='')",
' if i != len(s) - 1:',
" print(', ')",
' else:',
" print('')",
" print(']')",
'',
'def puq():',
' for i in s:',
' print(i)',
'pq()',
'puq()',
''
]
def pq():
print('s = [')
for i, v in enumerate(s):
print(' ' * 2 + repr(v), end='')
if i != len(s) - 1:
print(', ')
else:
print('')
print(']')
def puq():
for i in s:
print(i)
pq()
puq()